package org.nutz.mvc.view;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.nutz.lang.Encoding;
import org.nutz.lang.Lang;
import org.nutz.lang.Streams;
import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.View;

/**
 * 将数据对象直接写入 HTTP 响应
 * <p>
 * <h2>数据对象可以是如下类型:</h2>
 * <ol>
 * <li><b>null</b> - 什么都不做
 * <li><b>File</b> - 文件,以下载方法返回,文件名将自动设置
 * <li><b>byte[]</b> - 按二进制方式写入HTTP响应流
 * <li><b>InputStream</b> - 按二进制方式写入响应流，并关闭 InputStream
 * <li><b>char[]</b> - 按文本方式写入HTTP响应流
 * <li><b>Reader</b> - 按文本方式写入HTTP响应流，并关闭 Reader
 * <li><b>默认的</b> - 直接将对象 toString() 后按文本方式写入HTTP响应流
 * </ol>
 * <p>
 * <h2>ContentType 支持几种缩写:</h2>
 * <ul>
 * <li><b>xml</b> - 表示 <b>text/xml</b>
 * <li><b>html</b> - 表示 <b>text/html</b>
 * <li><b>htm</b> - 表示 <b>text/html</b>
 * <li><b>stream</b> - 表示 <b>application/octet-stream</b>
 * <li><b>默认的</b>(即 '@Ok("raw")' ) - 将采用 <b>ContentType=text/plain</b>
 * </ul>
 * 
 * @author wendal(wendal1985@gmail.com)
 * @author zozoh(zozohtnt@gmail.com)
 * 
 */
public class RawView implements View {

	private static final Log log = Logs.get();
	
	private static final int big4G = Integer.MAX_VALUE;
	
	public static final boolean DISABLE_RANGE_DOWNLOAD = false; //禁用断点续传

	private String contentType;

	public RawView(String contentType) {
		if (Strings.isBlank(contentType))
			contentType = "text/plain";
		this.contentType = Strings.sNull(contentTypeMap.get(contentType.toLowerCase()), contentType);
	}

	public void render(HttpServletRequest req, HttpServletResponse resp, Object obj)
			throws Throwable {
		// 如果用户自行设置了,那就不要再设置了!
		if (resp.getContentType() == null)
			resp.setContentType(contentType);
		if (obj == null)
			return;
		// 文件
		if (obj instanceof File) {
			File file = (File) obj;
			long fileSz = file.length();
			if (log.isDebugEnabled())
				log.debug("File downloading ... " + file.getAbsolutePath());
			if (!file.exists() || file.isDirectory()) {
				log.debug("File downloading ... Not Exist : " + file.getAbsolutePath());
				resp.sendError(404);
				return;
			}
			if (!resp.containsHeader("Content-Disposition")) {
				String filename = URLEncoder.encode(file.getName(), Encoding.UTF8);
				resp.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
			}
			
			String rangeStr = req.getHeader("Range");
			if (DISABLE_RANGE_DOWNLOAD || fileSz == 0 || (rangeStr == null || !rangeStr.startsWith("bytes=") || rangeStr.length() < "bytes=1".length())) {
				resp.setHeader("Content-Length", "" + fileSz);
				Streams.writeAndClose(resp.getOutputStream(), Streams.fileIn(file));
			} else {
				// log.debug("Range Download : " + req.getHeader("Range"));
				List<RangeRange> rs = new ArrayList<RawView.RangeRange>();
				if (!parseRange(rangeStr, rs, fileSz)) {
					resp.setStatus(416);
					return;
				}
				// 暂时只实现了单range
				if (rs.size() != 1) {
					// TODO 完成多range的下载
					log.info("multipart/byteranges is NOT support yet");
					resp.setStatus(416);
					return;
				}
				long totolSize = 0;
				for (RangeRange rangeRange : rs) {
					totolSize += (rangeRange.end - rangeRange.start);
				}
				resp.setStatus(206);
				resp.setHeader("Content-Length", "" + totolSize);
				resp.setHeader("Accept-Ranges", "bytes");
				
				// 暂时只有单range,so,简单起见吧
				RangeRange rangeRange = rs.get(0);
				resp.setHeader("Content-Range", String.format("bytes %d-%d/%d", rangeRange.start, rangeRange.end -1, fileSz));
				writeFileRange(file, resp.getOutputStream(), rangeRange);
			}
		}
		// 字节数组
		else if (obj instanceof byte[]) {
			resp.setHeader("Content-Length", "" + ((byte[]) obj).length);
			Streams.writeAndClose(resp.getOutputStream(), (byte[]) obj);
		}
		// 字符数组
		else if (obj instanceof char[]) {
			Writer writer = resp.getWriter();
			writer.write((char[]) obj);
			writer.flush();
		}
		// 文本流
		else if (obj instanceof Reader) {
			Streams.writeAndClose(resp.getWriter(), (Reader) obj);
		}
		// 二进制流
		else if (obj instanceof InputStream) {
			Streams.writeAndClose(resp.getOutputStream(), (InputStream) obj);
		}
		// 普通对象
		else {
			byte[] data = String.valueOf(obj).getBytes(Encoding.UTF8);
			resp.setHeader("Content-Length", "" + data.length);
			Streams.writeAndClose(resp.getOutputStream(), data);
		}
	}

	private static final Map<String, String> contentTypeMap = new HashMap<String, String>();

	static {
		contentTypeMap.put("xml", "application/xml");
		contentTypeMap.put("html", "text/html");
		contentTypeMap.put("htm", "text/html");
		contentTypeMap.put("stream", "application/octet-stream");
		contentTypeMap.put("js", "application/javascript");
		contentTypeMap.put("json", "application/json");
	}

	public static class RangeRange {
		public RangeRange(long start, long end) {
			this.start = start;
			this.end = end;
		}

		long start;
		long end = -1;

	}

	public static final boolean parseRange(String rangeStr, List<RangeRange> rs, long maxSize) {
		rangeStr = rangeStr.substring("bytes=".length());
		String[] ranges = rangeStr.split(",");
		for (String range : ranges) {
			if (range == null || Strings.isBlank(range)) {
				log.debug("Bad Range -->    " + rangeStr);
				return false;
			}
			range = range.trim();
			try {
				// 首先是从后往前算的 bytes=-100 取最后100个字节
				if (range.startsWith("-")) {

					// 注意,这里是负数
					long end = Long.parseLong(range);
					long start = maxSize + end;
					if (start < 0) {
						log.debug("Bad Range -->    " + rangeStr);
						return false;
					}
					rs.add(new RangeRange(start, maxSize));
					continue;
				}

				// 然后就是从开头到最后   bytes=1024-
				if (range.endsWith("-")) {
					// 注意,这里是负数
					long start = Long.parseLong(range.substring(0, range.length() - 1));
					if (start < 0) {
						log.debug("Bad Range -->    " + rangeStr);
						return false;
					}
					rs.add(new RangeRange(start, maxSize));
					continue;
				}
				
				// 哦也,是最标准的有头有尾?
				if (range.contains("-")) {
					String[] tmp = range.split("-");
					long start = Long.parseLong(tmp[0]);
					long end = Long.parseLong(tmp[1]);
					if (start > end) {
						log.debug("Bad Range -->    " + rangeStr);
						return false;
					}
					rs.add(new RangeRange(start, end + 1)); //这里需要调查一下
				} else {
					// 操!! 单个字节?!!
					long start = Long.parseLong(range);
					rs.add(new RangeRange(start, start+1));
				}
			}
			catch (Throwable e) {
				log.debug("Bad Range -->    " + rangeStr, e);
				return false;
			}
		}
		return !rs.isEmpty();
	}
	
	public static void writeFileRange(File file, OutputStream out, RangeRange rangeRange) {
		FileInputStream fin = null;
		try {
			fin = new FileInputStream(file);
			DataInputStream in = new DataInputStream(fin);
			if (rangeRange.start > 0) {
				long start = rangeRange.start;
				while (start > 0) {
					if (start > big4G) {
						start -= big4G;
						in.skipBytes(big4G);
					} else {
						in.skipBytes((int)start);
						break;
					}
				}
			}
			byte[] buf = new byte[8192];
			BufferedInputStream bin = new BufferedInputStream(in);
			long pos = rangeRange.start;
			int len = 0;
			while (pos < rangeRange.end) {
				if (rangeRange.end - pos > 8192) {
					len = bin.read(buf);
				} else {
					len = bin.read(buf, 0 , (int)(rangeRange.end - pos));
				}
				if (len == -1) {//有时候,非常巧合的,文件已经读取完,就悲剧开始了...
					break;
				}
				if (len > 0) {
					out.write(buf, 0, len);
					pos += len;
				}
			}
			out.flush();
		}
		catch (Throwable e) {
			throw Lang.wrapThrow(e);
		}
		finally {
			Streams.safeClose(fin);
		}
	}
}
